/*********************************************************************
Copyright 2011 Chris Gardner

This file is part of CardLib.

CardLib is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

CardLib is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with CardLib.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace FreestyleCoding.CardLib {

	/// <summary>A Deck of PokerCards</summary>
	public class PokerDeck : IDeck<IPlayingCard> {
		private List<IPlayingCard> m_Deck;
		private Random m_RandomNumberGenerator;

		/// <summary>Private constructor to initialize the private fields.</summary>
		private PokerDeck() {
			m_Deck = new List<IPlayingCard>();
			m_RandomNumberGenerator = new Random();
		}

		/// <summary>Creates a new deck of cards</summary>
		/// <returns>A new PokerDeck that does not include Jokers</returns>
		public static PokerDeck New() {
			return New( false );
		}

		/// <summary>Creates a new deck of cards</summary>
		/// <param name="p_IncludeJokers">true, if the new deck should include Jokers.</param>
		/// <returns>If <paramref name="p_IncludeJokers"/> is true, a new PokerDeck that includes Jokers.
		/// If <paramref name="p_IncludeJokers"/> is false, a new PokerDeck that does not include Jokers.</returns>
		public static PokerDeck New( bool p_IncludeJokers ) {
			PokerDeck _NewDeck = new PokerDeck();

			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ace, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Two, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Three, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Four, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Five, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Six, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Seven, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Eight, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Nine, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ten, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Jack, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Queen, Suits.Clubs, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.King, Suits.Clubs, null, null ) );

			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ace, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Two, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Three, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Four, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Five, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Six, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Seven, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Eight, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Nine, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ten, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Jack, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Queen, Suits.Diamonds, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.King, Suits.Diamonds, null, null ) );

			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ace, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Two, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Three, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Four, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Five, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Six, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Seven, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Eight, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Nine, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ten, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Jack, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Queen, Suits.Hearts, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.King, Suits.Hearts, null, null ) );

			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ace, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Two, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Three, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Four, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Five, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Six, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Seven, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Eight, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Nine, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Ten, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Jack, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.Queen, Suits.Spades, null, null ) );
			_NewDeck.m_Deck.Add( new PokerCard( Ranks.King, Suits.Spades, null, null ) );

			if( p_IncludeJokers ) {
				_NewDeck.m_Deck.Add( new PokerCard( Ranks.Joker, Suits.Spades, null, null ) );
				_NewDeck.m_Deck.Add( new PokerCard( Ranks.Joker, Suits.Hearts, null, null ) );
			}

			return _NewDeck;
		}

		/// <summary>Merges a single card into the PokerDeck</summary>
		/// <param name="p_CardToAddToDeck">The card to add.</param>
		public void Merge( IPlayingCard p_CardToAddToDeck ) {
			m_Deck.Add( p_CardToAddToDeck );
		}

		#region IDeck Members
		/// <summary>Cuts the deck</summary>
		/// <remarks>Picks a random place to cut</remarks>
		public void Cut() {
			Cut( ( m_RandomNumberGenerator.Next( 1, m_Deck.Count ) + m_RandomNumberGenerator.Next( 1, m_Deck.Count ) ) / 2 );
		}

		/// <summary>Cuts the deck</summary>
		/// <param name="p_NumberOfCardsToCutFromTheTop">Number of cards to pull of the top to cut</param>
		/// <exception cref="ArgumentOutOfBoundsException">p_NumberOfCardsToCutFromTheTop is negative</exception>
		/// <exception cref="ArgumentOutOfBoundsException">p_NumberOfCardsToCutFromTheTop is greater than Length of IDeck</exception>
		public void Cut( int p_NumberOfCardsToCutFromTheTop ) {
			if( p_NumberOfCardsToCutFromTheTop < 0 ) {
				throw new ArgumentOutOfRangeException( "p_NumberOfCardsToCutFromTheTop", "Param cannot be negative" );
			}

			if( p_NumberOfCardsToCutFromTheTop > m_Deck.Count ) {
				throw new ArgumentOutOfRangeException( "p_NumberOfCardsToCutFromTheTop", "Param cannot be greater than number of cards in the deck." );
			}

			Collection<IPlayingCard> _CutCards = Draw( p_NumberOfCardsToCutFromTheTop );
			m_Deck.AddRange( _CutCards );
		}

		/// <summary>Randomized the order of the cards in the deck</summary>
		public void Shuffle() {
			for( int _lcv = m_Deck.Count; _lcv > 0; --_lcv ) {
				IPlayingCard _Removed = m_Deck[m_RandomNumberGenerator.Next( 0, _lcv )];
				m_Deck.Remove( _Removed );
				m_Deck.Add( _Removed );
			}
		}

		/// <summary>Draws the top card from the deck. The card is removed from the deck when it is drawn.</summary>
		/// <returns>The top ICard.
		/// If the deck is empty, returns null</returns>
		public IPlayingCard Draw() {
			if( HasCards ) {
				IPlayingCard _DrawnCard = m_Deck[0];
				m_Deck.RemoveAt( 0 );
				return _DrawnCard;
			} else {
				return null;
			}
		}

		/// <summary>Draws the top <paramref name="p_NumberOfCardsToDraw"/> cards from the deck. The cards are removed from the deck when they are drawn.</summary>
		/// <param name="p_NumberOfCardsToDraw">New number of cards to remove from the deck</param>
		/// <returns>A collections of ICards with a Length of <paramref name="p_NumberOfCardsToDraw"/>.
		/// If the IDeck currently has less than <paramref name="p_NumberOfCardsToDraw"/> cards, all cards are returned and the IDeck will be empty.
		/// If the deck is empty, return null.</returns>
		public Collection<IPlayingCard> Draw( int p_NumberOfCardsToDraw ) {
			if( !HasCards ) {
				return null;
			}

			Collection<IPlayingCard> _DrawnCards =new Collection<IPlayingCard>();
			for( ; HasCards && p_NumberOfCardsToDraw > 0; --p_NumberOfCardsToDraw ) {
				_DrawnCards.Add( m_Deck[0] );
				m_Deck.RemoveAt( 0 );
			}

			return _DrawnCards;
		}

		/// <summary>Inserts the card onto the top of the deck</summary>
		/// <param name="p_CardToInsert">The card to insert into the deck</param>
		public void Insert( ICard p_CardToInsert ) {
			Insert( p_CardToInsert, 0 );
		}

		/// <summary>Inserts the card into the specific place in the deck</summary>
		/// <param name="p_CardToInsert">The card to insert into the deck</param>
		/// <param name="p_Position">The position into the deck to place the card. If p_Position is negative or zero, it is inserted on top of the deck. If p_Position is greator than the size of the deck, it is placed as the last card.</param>
		/// <exception cref="">p_CardToInsert is not an IPlayingCard</exception>
		public void Insert( ICard p_CardToInsert, int p_Position ) {
			IPlayingCard _playingCard = p_CardToInsert as IPlayingCard;
			if( _playingCard == null ) {
				throw new InvalidCastException( "p_CardToInsert is not an IPlayingCard" );
			}

			if( p_Position < 1 ) {
				m_Deck.Insert( 0, _playingCard );
			} else if( p_Position > m_Deck.Count ) {
				m_Deck.Add( _playingCard );
			} else {
				m_Deck.Insert( p_Position, _playingCard );
			}
		}

		/// <summary>Places all the cards in <paramref name="p_DeckToInsertIntoThisDeck"/> into this IDeck.</summary>
		/// <param name="p_DeckToInsertIntoThisDeck">An IDeck to be merged with the current deck.</param>
		/// <remarks><paramref name="p_DeckToInsertIntoThisDeck"/> will be empty after this operation.</remarks>
		public void Merge( IDeck<IPlayingCard> p_DeckToInsertIntoThisDeck ) {
			m_Deck.AddRange( p_DeckToInsertIntoThisDeck );

			// We're dealing with an interface, so we have to call draw.
			// We could use Draw( int ), but it just loops, too...
			while( p_DeckToInsertIntoThisDeck.HasCards ) {
				p_DeckToInsertIntoThisDeck.Draw();
			}
		}

		/// <summary>Specifies if the IDeck has any cards.</summary>
		/// <returns>true if the IDeck has cards, false if the IDeck is empty.</returns>
		public bool HasCards {
			get { return m_Deck.Count > 0; }
		}
		#endregion

		#region IEnumerable<IPlayingCard> Members

		public IEnumerator<IPlayingCard> GetEnumerator() {
			return m_Deck.GetEnumerator();
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
			return m_Deck.GetEnumerator();
		}

		#endregion
	}
}
